﻿#region Using directives

using System;
using System.Text;
using System.CodeDom;
using System.Collections.Generic;


using Weazel.Gezel.Model;
using Weazel.Gezel.Conversions.CodeGeneration;
using GezelModel = Weazel.Gezel.Model.Model;

#endregion

namespace Weazel.Gezel.Conversions.Model
{
  public class ControllerType : Type
  {
    private Method evaluateMethod;
    private Method initializeMethod;

    private Field currentControlStep;
    public Field CurrentControlStep
    {
      get
      {
        return currentControlStep;
      }
    }

    private Field currentState;

    /// <summary>
    /// The list of sets of signal flow graphs is
    /// used to determine which sfg's are activated
    /// at the same time for each control step.
    /// </summary>
    private readonly List<ExecutionSet> executionSets;
    public List<ExecutionSet> ExecutionSets
    {
      get
      {
        return executionSets;
      }
    }

    private Controller controller;
    public Controller Controller
    {
      get { return controller; }
    }

    private Dictionary<string, OutPortType> outportTypes;
    public Dictionary<string, OutPortType> OutportTypes
    {
      get { return outportTypes; }
    }

    private Dictionary<string, RegisterType> registerTypes;
    public Dictionary<string, RegisterType> RegisterTypes
    {
      get { return registerTypes; }
    }

    private Dictionary<string, SignalType> signalTypes;
    public Dictionary<string, SignalType> SignalTypes
    {
      get { return signalTypes; }
    }

    public ControllerType(Controller gezelController, SoftwareModel softwareModel)
      : base(String.Format("Controller_{0}", gezelController.Name.Replace(".", String.Empty)), softwareModel)
    {
      this.controller = gezelController;
      this.executionSets = ControllerConversionHelper.getControlSteps(softwareModel, gezelController);
      this.BaseTypes.Add(new CodeTypeReference("IController"));

      this.outportTypes = new Dictionary<string, OutPortType>();
      this.registerTypes = new Dictionary<string, RegisterType>();
      this.signalTypes = new Dictionary<string, SignalType>();

      this.comments["summary"] = string.Empty;
      this.comments["controllertype"] = gezelController.Type.ToString();

      currentControlStep = AddField("CurrentControlStep", "Int32");
      currentControlStep.Comments.Add(new CodeCommentStatement("The control-step the controller is executing."));
    }

    private void buildInitializeMethod()
    {
      initializeMethod = AddMethod("Initialize");

      // initialize 'CurrentControlStep' field
      CodeFieldReferenceExpression currentControlStepField = new CodeFieldReferenceExpression(new CodeThisReferenceExpression(), currentControlStep.Name);

      if (controller.Type == Controller.ControllerType.Sequencer)
      {
        // if we have a sequencer, we will have to initialize the control step to the last, such
        // that upon first evaluation, the very first controlstep will be selected as the next.
        int lastControlStep = executionSets.Count - 1;
        initializeMethod.Statements.Add(new CodeAssignStatement(currentControlStepField, new CodePrimitiveExpression(lastControlStep)));
      }
      else
      {
        initializeMethod.Statements.Add(new CodeAssignStatement(currentControlStepField, new CodePrimitiveExpression(0)));
      }

      if (controller.Type == Controller.ControllerType.Fsm)
      {
        //initializeMethod.Statements.Add(new CodeSnippetStatement(string.Format("{0} = {1};", currentState.Name, controller.InitialState.Id)));
        CodeFieldReferenceExpression currentStateField =
          new CodeFieldReferenceExpression(
            new CodeThisReferenceExpression(),
            currentState.Name
          );
        initializeMethod.Statements.Add(new CodeAssignStatement(currentStateField, new CodePrimitiveExpression(controller.InitialState.Id)));
      }
      initializeMethod.Statements.Add(Statement.Empty);

      // initialize integer fields
      foreach (Field field in Fields.Values)
      {
        IntegerField integerField = field as IntegerField;
        if (integerField != null)
        {
          if (integerField.InitialValue != "0")
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.InitialValue),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
          else
          {
            initializeMethod.Statements.Add(
              new CodeAssignStatement(
                new CodeFieldReferenceExpression(
                  new CodeThisReferenceExpression(),
                  integerField.Name
                ),
                new CodeObjectCreateExpression(
                  "Integer",
                  new CodePrimitiveExpression(integerField.Width),
                  new CodePrimitiveExpression(integerField.Signed ? true : false)
                )
              )
            );
          }
        }
      }
    }

    public void AnalyzeDependencies()
    {
      foreach (ExecutionSet sfgSet in executionSets)
      {
        sfgSet.AnalyzeDependencies();
      }
    }

    /// <summary>
    /// Runs operations which are dependent
    /// on the analysis step being competed
    /// </summary>
    internal void PostAnalyzeDependencies()
    {
      this.comments["executionsets"] = executionSetsToString();

      if (controller.Type == Controller.ControllerType.Fsm)
        buildFsmExecuteMethod();
      else if (controller.Type == Controller.ControllerType.Hardwired)
        buildHardwiredExecuteMethod();
      else if (controller.Type == Controller.ControllerType.Sequencer)
        buildSequencerExecuteMethod();

      buildInitializeMethod();
    }

    public int[] GetControlStepsForSfg(SignalFlowGraph sfg)
    {
      List<int> controlSteps = new List<int>();
      foreach (ExecutionSet sfgSet in executionSets)
      {
        if (sfgSet.Contains(sfg))
          controlSteps.Add(sfgSet.ControlStepId);
      }
      return controlSteps.ToArray();
    }

    private string executionSetsToString()
    {
      StringBuilder builder = new StringBuilder();
      foreach (ExecutionSet sfgSet in executionSets)
      {
        builder.Append(sfgSet.ToString());
      }
      return builder.ToString();
    }

    private void buildFsmExecuteMethod()
    {
      // this variable is only needed on fsm controllers
      currentState = AddField("CurrentState", "Int32");
      currentControlStep.Comments.Add(new CodeCommentStatement("The state the controller is currently in."));

      evaluateMethod = AddMethod("Evaluate");

      CodeStatementCollection statements = new CodeStatementCollection();
      statements.Add(new CodeSnippetStatement(string.Format("  switch({0})", currentState.Name)));
      statements.Add(Statement.OpenBracket);

      int controlStep = 0;

      foreach (State state in controller.States.Values)
      {
        Transition transition = controller.Transitions[state.LocalName];

        statements.Add(new CodeSnippetStatement(string.Format("  case {0}:", state.Id)));
        statements.AddRange(buildTransitionRecursive(transition, ref controlStep, 0));
        statements.Add(new CodeSnippetStatement("    break;"));
      }

      statements.Add(new CodeSnippetStatement("  default: // should never get here"));
      statements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("ApplicationException")));
      statements.Add(Statement.CloseBracket);

      evaluateMethod.Statements.AddRange(statements);
    }

    private CodeStatementCollection buildTransitionRecursive(Transition transition, ref int controlStep, int recursionDepth)
    {
      CodeStatementCollection statements = new CodeStatementCollection();

      // handle the three different transition types separately
      if (transition.TransitionType == TransitionType.SimpleUnconditional)
      {
        statements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentState.Name, controller.States[transition.FirstTargetState].Id)));
        statements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentControlStep.Name, controlStep++)));

        enqueueOutports(controlStep, statements);

        return statements;
      }
      else if (transition.TransitionType == TransitionType.SimpleConditional)
      {
        CodeFieldReferenceExpression fieldReference = ExpressionConversionHelper.BuildEvaluateMethodRecursive(transition.Condition, statements, this);
        CodeFieldReferenceExpression zeroReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Zero");
        CodeConditionStatement conditionStatement = new CodeConditionStatement();
        statements.Add(conditionStatement);
        if (softwareModel.Language != TargetLanguage.IsoCpp)
        {
          CodeBinaryOperatorExpression conditionExpression = new CodeBinaryOperatorExpression(zeroReference, CodeBinaryOperatorType.IdentityInequality, fieldReference);
          conditionStatement.Condition = conditionExpression;
        }
        else
        {
          CodeMethodInvokeExpression conditionExpression = new CodeMethodInvokeExpression(zeroReference, "NotEquals", fieldReference);
          conditionStatement.Condition = conditionExpression;
        }

        conditionStatement.TrueStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentState.Name, controller.States[transition.FirstTargetState].Id)));
        conditionStatement.TrueStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentControlStep.Name, controlStep)));

        enqueueOutports(controlStep, conditionStatement.TrueStatements);
        controlStep++;

        conditionStatement.FalseStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentState.Name, controller.States[transition.SecondTargetState].Id)));
        conditionStatement.FalseStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentControlStep.Name, controlStep)));

        enqueueOutports(controlStep, conditionStatement.FalseStatements);
        controlStep++;

        statements.Add(conditionStatement);
        return statements;
      }
      else if (transition.TransitionType == TransitionType.Nested)
      {
        CodeConditionStatement conditionStatement = new CodeConditionStatement();
        if (transition.FirstTransition.TransitionType == TransitionType.SimpleUnconditional)
        {
          CodeFieldReferenceExpression fieldReference = ExpressionConversionHelper.BuildEvaluateMethodRecursive(transition.Condition, statements, this);
          CodeFieldReferenceExpression zeroReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Zero");
          if (softwareModel.Language != TargetLanguage.IsoCpp)
          {
            CodeBinaryOperatorExpression conditionExpression = new CodeBinaryOperatorExpression(zeroReference, CodeBinaryOperatorType.IdentityInequality, fieldReference);
            conditionStatement.Condition = conditionExpression;
          }
          else
          {
            CodeMethodInvokeExpression conditionExpression = new CodeMethodInvokeExpression(zeroReference, "NotEquals", fieldReference);
            conditionStatement.Condition = conditionExpression;
          }

          conditionStatement.TrueStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentState.Name, controller.States[transition.FirstTransition.FirstTargetState].Id)));
          conditionStatement.TrueStatements.Add(new CodeSnippetStatement(string.Format("  {0} = {1};", currentControlStep.Name, controlStep)));

          enqueueOutports(controlStep, conditionStatement.TrueStatements);

          controlStep++;
        }
        else
        {
          // recurse
          CodeFieldReferenceExpression fieldReference = ExpressionConversionHelper.BuildEvaluateMethodRecursive(transition.Condition, statements, this);
          CodeFieldReferenceExpression zeroReference = new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Integer"), "Zero");
          if (softwareModel.Language != TargetLanguage.IsoCpp)
          {
            CodeBinaryOperatorExpression conditionExpression = new CodeBinaryOperatorExpression(zeroReference, CodeBinaryOperatorType.IdentityInequality, fieldReference);
            conditionStatement.Condition = conditionExpression;
          }
          else
          {
            CodeMethodInvokeExpression conditionExpression = new CodeMethodInvokeExpression(zeroReference, "NotEquals", fieldReference);
            conditionStatement.Condition = conditionExpression;
          }

          conditionStatement.TrueStatements.AddRange(buildTransitionRecursive(transition.FirstTransition, ref controlStep, recursionDepth + 1));
        }

        // recurse
        conditionStatement.FalseStatements.AddRange(buildTransitionRecursive(transition.SecondTransition, ref controlStep, recursionDepth + 1));

        statements.Add(conditionStatement);
        return statements;
      }
      else
        throw new ApplicationException("unsupported fsm-transition type");
    }

    private void buildHardwiredExecuteMethod()
    {
      evaluateMethod = AddMethod("Evaluate");

      enqueueOutports(1, evaluateMethod.Statements);
    }

    private void buildSequencerExecuteMethod()
    {
      evaluateMethod = AddMethod("Evaluate");

      evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("  switch({0})", currentControlStep.Name)));
      evaluateMethod.Statements.Add(Statement.OpenBracket);

      for (int controlStep = 0; controlStep < controller.SignalFlowGraphRefs.Count; controlStep++)
      {
        evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("  case {0}:", controlStep)));
        if (controlStep == controller.SignalFlowGraphRefs.Count - 1)
          evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("    {0} = 0;", currentControlStep.Name)));
        else
          evaluateMethod.Statements.Add(new CodeSnippetStatement(string.Format("    {0} = {1};", currentControlStep.Name, controlStep + 1)));

        enqueueOutports(controlStep + 1, evaluateMethod.Statements);
        evaluateMethod.Statements.Add(new CodeSnippetStatement("    break;"));
      }

      evaluateMethod.Statements.Add(new CodeSnippetStatement("  default: // should never get here"));
      evaluateMethod.Statements.Add(new CodeThrowExceptionStatement(new CodeObjectCreateExpression("ApplicationException")));
      evaluateMethod.Statements.Add(Statement.CloseBracket);
    }

    private void enqueueOutports(int controlStep, CodeStatementCollection statements)
    {
      foreach (OutPortType outportType in outportTypes.Values)
      {
        if (softwareModel.Language != TargetLanguage.IsoCpp)
        {
          // produces
          // Simulator.OutportList.Add(Simulator.Outport_xxx);
          statements.Add(
            new CodeMethodInvokeExpression(
              new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "Add"),
              new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
            )
          );
        }
        else // C++ special case
        {
          // produces
          // Simulator::OutportList->push_back(Simulator::Outport_xxx);
          statements.Add(
            new CodeMethodInvokeExpression(
              new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "push_back"),
              new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
            )
          );
        }
      }

      //// get previous controlstep
      //int lastControlStep = controlStep == 0 ? executionSets.Count - 1 : controlStep - 1;
      //ExecutionSet executionSet = executionSets[lastControlStep];

      //foreach (RegisterType registerType in registerTypes.Values)
      //{
      //  statements.Add(new CodeCommentStatement(String.Format("dependent outports through register {0} for controlStep {1}", registerType.Name, controlStep)));
      //  foreach (OutPortType outportType in registerType.OutportDependencies[lastControlStep])
      //  {
      //    if (softwareModel.Language != TargetLanguage.IsoCpp)
      //    {
      //      // produces
      //      // Simulator.OutportList.Add(Simulator.Outport_xxx);
      //      statements.Add(
      //        new CodeMethodInvokeExpression(
      //          new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "Add"),
      //          new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
      //        )
      //      );
      //    }
      //    else // C++ special case
      //    {
      //      // produces
      //      // Simulator::OutportList->push_back(Simulator::Outport_xxx);
      //      statements.Add(
      //        new CodeMethodInvokeExpression(
      //          new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "push_back"),
      //          new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
      //        )
      //      );
      //    }
      //  }
      //}

      //foreach (SignalType signalType in executionSet.SignalDependencies)
      //{
      //  statements.Add(new CodeCommentStatement(string.Format("dependent outports through signal {0}", signalType.Name)));
      //  foreach (OutPortType outportType in signalType.OutportDependencies[lastControlStep])
      //  {
      //    if (softwareModel.Language != TargetLanguage.IsoCpp)
      //    {
      //      // produces
      //      // Simulator.OutportList.Add(Simulator.Outport_xxx);
      //      statements.Add(
      //        new CodeMethodInvokeExpression(
      //          new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "Add"),
      //          new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
      //        )
      //      );
      //    }
      //    else // C++ special case
      //    {
      //      // produces
      //      // Simulator::OutportList->push_back(Simulator::Outport_xxx);
      //      statements.Add(
      //        new CodeMethodInvokeExpression(
      //          new CodeMethodReferenceExpression(new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), "OutportList"), "push_back"),
      //          new CodeFieldReferenceExpression(new CodeTypeReferenceExpression("Simulator"), outportType.InstanceName)
      //        )
      //      );
      //    }
      //  }
      //}
    }
  }
}
